/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley.toolbox;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * A variation of {@link java.io.ByteArrayOutputStream} that uses a pool of byte[] buffers instead
 * of always allocating them fresh, saving on heap churn.
 */
public class PoolingByteArrayOutputStream extends ByteArrayOutputStream {
	/**
	 * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor
	 * is called, this is the default size to which the underlying byte array is
	 * initialized.
	 */
	private static final int DEFAULT_SIZE = 256;

	private final ByteArrayPool mPool;

	/**
	 * Constructs a new PoolingByteArrayOutputStream with a default size. If
	 * more bytes are written to this instance, the underlying byte array will
	 * expand.
	 */
	public PoolingByteArrayOutputStream(ByteArrayPool pool) {
		this(pool, DEFAULT_SIZE);
	}

	/**
	 * Constructs a new {@code ByteArrayOutputStream} with a default size of
	 * {@code size} bytes. If more than {@code size} bytes are written to this
	 * instance, the underlying byte array will expand.
	 * 
	 * @param size
	 *            initial size for the underlying byte array. The value will be
	 *            pinned to a default minimum size.
	 */
	public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
		mPool = pool;
		buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE));
	}

	@Override
	public void close() throws IOException {
		mPool.returnBuf(buf);
		buf = null;
		super.close();
	}

	@Override
	public void finalize() {
		mPool.returnBuf(buf);
	}

	/**
	 * Ensures there is enough space in the buffer for the given number of
	 * additional bytes.
	 */
	private void expand(int i) {
		/* Can the buffer handle @i more bytes, if not expand it */
		if (count + i <= buf.length) {
			return;
		}
		byte[] newbuf = mPool.getBuf((count + i) * 2);
		System.arraycopy(buf, 0, newbuf, 0, count);
		mPool.returnBuf(buf);
		buf = newbuf;
	}

	@Override
	public synchronized void write(byte[] buffer, int offset, int len) {
		expand(len);
		super.write(buffer, offset, len);
	}

	@Override
	public synchronized void write(int oneByte) {
		expand(1);
		super.write(oneByte);
	}
}
